using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Reflection;

namespace Microsoft.MSE.Data
{
    internal delegate void DataReaderClosedEventHandler();

    /// <summary>
    /// </summary>
    public class DataReader : IDataReader
    {
        internal DataReaderClosedEventHandler DataReaderClosed;
        private readonly SqlDataReader _reader;

        public DataReader(SqlDataReader reader)
        {
            _reader = reader;
        }

        public int FieldCount
        {
            get { return _reader.FieldCount; }
        }

        public bool GetBoolean(string name)
        {
            return GetBoolean(_reader.GetOrdinal(name));
        }

        public bool GetBoolean(int i)
        {
            return _reader.GetBoolean(i);
        }

        public byte GetByte(string name)
        {
            return GetByte(_reader.GetOrdinal(name));
        }

        public byte GetByte(int i)
        {
            return _reader.GetByte(i);
        }

        public long GetBytes(string name, long fieldOffset, byte[] buffer, int bufferOffset, int length)
        {
            return GetBytes(_reader.GetOrdinal(name), fieldOffset, buffer, bufferOffset, length);
        }

        public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferOffset, int length)
        {
            return _reader.GetBytes(i, fieldOffset, buffer, bufferOffset, length);
        }

        public char GetChar(string name)
        {
            return GetChar(_reader.GetOrdinal(name));
        }

        public char GetChar(int i)
        {
            return _reader.GetChar(i);
        }

        public long GetChars(string name, long fieldOffset, char[] buffer, int bufferOffset, int length)
        {
            return GetChars(_reader.GetOrdinal(name), fieldOffset, buffer, bufferOffset, length);
        }

        public long GetChars(int i, long fieldOffset, char[] buffer, int bufferOffset, int length)
        {
            return _reader.GetChars(i, fieldOffset, buffer, bufferOffset, length);
        }

        public IDataReader GetData(string name)
        {
            return GetData(_reader.GetOrdinal(name));
        }

        public IDataReader GetData(int i)
        {
            return _reader.GetData(i);
        }

        public string GetDataTypeName(string name)
        {
            return GetDataTypeName(_reader.GetOrdinal(name));
        }

        public string GetDataTypeName(int i)
        {
            return _reader.GetDataTypeName(i);
        }

        public DateTime GetDateTime(string name)
        {
            return GetDateTime(_reader.GetOrdinal(name));
        }

        public DateTime GetDateTime(int i)
        {
            return _reader.GetDateTime(i);
        }

        public decimal GetDecimal(string name)
        {
            return GetDecimal(_reader.GetOrdinal(name));
        }

        public decimal GetDecimal(int i)
        {
            return _reader.GetDecimal(i);
        }

        public double GetDouble(string name)
        {
            return GetDouble(_reader.GetOrdinal(name));
        }

        public double GetDouble(int i)
        {
            return _reader.GetDouble(i);
        }

        public Type GetFieldType(string name)
        {
            return GetFieldType(_reader.GetOrdinal(name));
        }

        public Type GetFieldType(int i)
        {
            return _reader.GetFieldType(i);
        }

        public float GetFloat(string name)
        {
            return GetFloat(_reader.GetOrdinal(name));
        }

        public float GetFloat(int i)
        {
            return _reader.GetFloat(i);
        }

        public Guid GetGuid(string name)
        {
            return GetGuid(_reader.GetOrdinal(name));
        }

        public Guid GetGuid(int i)
        {
            return _reader.GetGuid(i);
        }

        public Int16 GetInt16(string name)
        {
            return GetInt16(_reader.GetOrdinal(name));
        }

        public Int16 GetInt16(int i)
        {
            return _reader.GetInt16(i);
        }

        public Int32 GetInt32(string name)
        {
            return GetInt32(_reader.GetOrdinal(name));
        }

        public Int32 GetInt32(int i)
        {
            return _reader.GetInt32(i);
        }

        public Int64 GetInt64(string name)
        {
            return GetInt64(_reader.GetOrdinal(name));
        }

        public Int64 GetInt64(int i)
        {
            return _reader.GetInt64(i);
        }

        public string GetName(int i)
        {
            return _reader.GetName(i);
        }

        public int GetOrdinal(string name)
        {
            return _reader.GetOrdinal(name);
        }

        public string GetString(string name)
        {
            return GetString(_reader.GetOrdinal(name));
        }

        public string GetString(int i)
        {
            return _reader.GetString(i);
        }

        public object GetValue(string name)
        {
            return GetValue(_reader.GetOrdinal(name));
        }

        public object GetValue(int i)
        {
            return _reader.GetValue(i);
        }

        public int GetValues(object[] values)
        {
            return _reader.GetValues(values);
        }

        public bool IsDBNull(string name)
        {
            return IsDBNull(_reader.GetOrdinal(name));
        }

        public bool IsDBNull(int i)
        {
            return _reader.IsDBNull(i);
        }

        public object this[string name]
        {
            get { return _reader[name]; }
        }

        public object this[int i]
        {
            get { return _reader[i]; }
        }

        public T? GetNullable<T>(string paramName) where T : struct
        {
            return GetNullable<T>(_reader.GetOrdinal(paramName));
        }

        public T? GetNullable<T>(int i) where T : struct
        {
            T? data = null;

            if (!_reader.IsDBNull(i))
            {
                data = (T?) _reader.GetValue(i);
            }

            return data;
        }

        public string GetNullableString(string name)
        {
            return GetNullableString(_reader.GetOrdinal(name));
        }

        public string GetNullableString(int i)
        {
            string data = null;

            if (!_reader.IsDBNull(i))
            {
                data = _reader.GetString(i);
            }
            return data;
        }

        public byte[] GetNullableBinary(string name)
        {
            return GetNullableBinary(_reader.GetOrdinal(name));
        }

        public byte[] GetNullableBinary(int i)
        {
            byte[] data = null;
            if (!_reader.IsDBNull(i))
            {
                SqlDataReader r = _reader;
                SqlBinary b = r.GetSqlBinary(i);
                data = b.Value;

            }
            return data;
        }

        public void Dispose()
        {
            _reader.Dispose();

            if (DataReaderClosed != null)
            {
                DataReaderClosed();
            }
        }

        public int RecordsAffected
        {
            get { return _reader.RecordsAffected; }
        }

        public bool Read()
        {
            return _reader.Read();
        }

        public bool NextResult()
        {
            return _reader.NextResult();
        }

        public bool IsClosed
        {
            get { return _reader.IsClosed; }
        }

        public DataTable GetSchemaTable()
        {
            return _reader.GetSchemaTable();
        }

        public int Depth
        {
            get { return _reader.Depth; }
        }

        public void Close()
        {
            _reader.Close();

            if (DataReaderClosed != null)
            {
                DataReaderClosed();
            }
        }

        public void Fill(object obj)
        {
            Fill(obj, false);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="useName"></param>
        public void Fill(object obj, bool useName)
        {
            // TODO: add support for properties

            FieldInfo[] fieldInfoList = obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public);
            int max = FieldCount < fieldInfoList.Length ? FieldCount : fieldInfoList.Length;

            for (int i = 0; i < max; i++)
            {
                FieldInfo fieldInfo = fieldInfoList[i];
                Type fldType = fieldInfo.FieldType;
                object val;
                int j = useName ? GetOrdinal(fieldInfo.Name) : i;

                if (fldType == typeof (int))
                {
                    val = GetInt32(j);
                }
                else if (fldType == typeof (decimal))
                {
                    val = GetDecimal(j);
                }
                else if (fldType == typeof (string))
                {
                    val = GetString(j);
                }
                else if (fldType == typeof (DateTime))
                {
                    val = GetDateTime(j);
                }
                else if (fldType == typeof (byte[]))
                {
                    val = GetNullableBinary(j);
                }
                else if (fldType == typeof (bool?))
                {
                    val = GetNullable<bool>(j);
                }
                else if (fldType == typeof (byte?))
                {
                    val = GetNullable<byte>(j);
                }
                else if (fldType == typeof (Guid?))
                {
                    val = GetNullable<Guid>(j);
                }
                else if (fldType == typeof (short?))
                {
                    val = GetNullable<short>(j);
                }
                else if (fldType == typeof (int?))
                {
                    val = GetNullable<int>(j);
                }
                else if (fldType == typeof (long?))
                {
                    val = GetNullable<long>(j);
                }
                else if (fldType == typeof (decimal?))
                {
                    val = GetNullable<decimal>(j);
                }
                else if (fldType == typeof (double?))
                {
                    val = GetNullable<double>(j);
                }
                else if (fldType == typeof (DateTime?))
                {
                    val = GetNullable<DateTime>(j);
                }
                else
                {
                    throw new NotSupportedException("Type (" + fldType.FullName + ") not supported.");
                }

                fieldInfo.SetValue(obj, val);
            }
        }
    }
}